add bundled provides guidance for srpm#92
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR adds support for representing RPM spec-declared bundled/vendored components (declared via ChangesBundled dependencies feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sbom/examples/rpm/build/bundled_provides.py`:
- Around line 42-45: The _bundled_purl function currently inserts dep.path
verbatim causing ambiguity for scoped npm names (e.g., `@scope/name`) —
percent-encode the path segment before building the purl (encode the leading @
as %40 and other reserved characters, while preserving path separators) by using
urllib.parse.quote with safe='/' (or equivalent) and then return
f"pkg:{purl_type}/{encoded_path}{ver}"; update the function (_bundled_purl) and
add the urllib.parse import (or your project's URL-encoding helper) so scoped
npm provides become pkg:npm/%40scope/name@...
In `@sbom/examples/rpm/build/from-koji.py`:
- Around line 598-601: Currently bundled_cdx_components are appended without
checking for existing bom-ref collisions, which can duplicate CycloneDX
component ids; before appending in the loop inside the routine that handles
bundled_cdx_components, compute an existing_bom_refs set from
self.cdx_components (use keys "bom-ref" and fallback to "purl"), then filter
bundled_cdx_components to skip any component whose "bom-ref" (from
_bundled_purl(b) / bundled_provides_to_cdx_components output) is already
present; keep the existing logic that pops version when None, but only append
components whose bom-ref is not in existing_bom_refs so create_cdx_from_spdx()
and the cdx["dependencies"][0]["dependsOn"] entries remain unambiguous.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Enterprise
Run ID: 0bd410dd-94ba-4da2-96bc-0d0a10e76c5a
📒 Files selected for processing (8)
docs/sbom.mdsbom/examples/rpm/build/README.mdsbom/examples/rpm/build/bundled_provides.pysbom/examples/rpm/build/from-koji.pysbom/examples/rpm/build/vim-9.1.083-5.el10.cdx.jsonsbom/examples/rpm/build/vim-9.1.083-5.el10.spdx.jsonsbom/examples/rpm/release/vim-9.1.083-5.el10.cdx.jsonsbom/examples/rpm/release/vim-9.1.083-5.el10.spdx.json
| def _bundled_purl(dep: BundledDep) -> str: | ||
| purl_type = LANG_TO_PURL_TYPE.get(dep.lang, "generic") | ||
| ver = f"@{dep.version}" if dep.version else "" | ||
| return f"pkg:{purl_type}/{dep.path}{ver}" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="sbom/examples/rpm/build/bundled_provides.py"
echo "== File: $FILE =="
sed -n '1,120p' "$FILE" | nl -ba | sed -n '1,120p'
echo
echo "== Lines around _bundled_purl (35-80) =="
sed -n '35,80p' "$FILE" | nl -ba
echo
echo "== Find BundledDep and dep.path construction =="
rg -n "class BundledDep|def _bundled_purl|dep\.path|_dep_lang_from_inner|LANG_TO_PURL_TYPE" "$FILE" -n
echo
echo "== Show full definitions related to bundling/parsing (where dep.path comes from) =="
# print likely relevant sections
sed -n '1,200p' "$FILE" | nl -ba | sed -n '1,200p'Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 207
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="sbom/examples/rpm/build/bundled_provides.py"
echo "== File head (first 160 lines) =="
sed -n '1,160p' "$FILE" | awk '{printf "%6d\t%s\n", NR, $0}'
echo
echo "== Lines around _bundled_purl (lines 30-90 approx) =="
sed -n '30,90p' "$FILE" | awk '{printf "%6d\t%s\n", NR+29, $0}'
echo
echo "== Grep key symbols =="
rg -n "class BundledDep|def _bundled_purl|dep\.path|_dep_lang_from_inner|LANG_TO_PURL_TYPE" "$FILE"
echo
echo "== Dump section that defines BundledDep and helpers (look 1-140) =="
sed -n '1,140p' "$FILE" | awk '{printf "%6d\t%s\n", NR, $0}'Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 13993
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "bundled\\(nodejs|golang\\(|bundled\\(|golang\\(" sbom/examples/rpm/build -S || true
rg -n "nodejs\\(|nodejs-\\w+|`@scope`|`@angular`|`@react`" sbom/examples/rpm/build -S || true
rg -n "bundled\\(" -S sbom/examples | head -n 200 || true
ls -R sbom/examples/rpm/build | head -n 200 || trueRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 2281
🌐 Web query:
Package URL (purl) npm scoped package @scope ambiguity version delimiter percent-encoding guidance for namespace/name
💡 Result:
In the Package URL (purl) specification, to represent an npm scoped package (which typically uses the format @scope/name), the '@' character in the scope must be percent-encoded as %40 [1][2][3]. This is required to avoid ambiguity with the '@' character used as the version delimiter, which appears later in the purl syntax [2][3]. According to the standard purl syntax—pkg:npm//@—the version is separated from the package identifier by an unencoded '@' [4][2][3]. If the '@' symbol at the start of a scope were not encoded, parsers might incorrectly identify it as the start of the version string [3][5]. Therefore, a scoped package such as @storybook/addon is represented as pkg:npm/%40storybook/addon [1][3]. Key guidance for encoding namespaces and names in purl: - Scopes (namespaces): The leading '@' must be encoded as %40 [1][3]. - Separators: The '/' that separates the namespace (scope) from the name does not need to be and must not be percent-encoded [2][5]. - Version Delimiter: The '@' used to separate the package identifier from the version string is a structural separator and should not be encoded in that specific position [2][5]. - Consistency: The general rule for purl is that structural characters are only encoded if they appear outside their defined functional positions, where they would otherwise cause parsing ambiguity [2]. By using %40 for the scope's '@', the resulting PURL remains unambiguous and conformant with the specification [2][3].
Citations:
- 1: https://help.sonatype.com/en/package-url-and-component-identifiers.html
- 2: https://spdx.github.io/spdx-spec/v3.0.1/annexes/pkg-url-specification/
- 3: https://github.com/package-url/purl-spec/blob/main/types-doc/npm-definition.md
- 4: https://ecma-tc54.github.io/ECMA-427/multipage/purl-specification.html
- 5: Percent encoding spec and : and / package-url/purl-spec#39
🌐 Web query:
SPDX externalRef purl encoding rules pkg:type/namespace/name@version percent-encoding reserved characters
💡 Result:
When using a Package URL (PURL) in an SPDX ExternalRef (referenceType: purl), the PURL must be encoded as an ASCII string using UTF-8 and percent-encoding as defined by RFC 3986 [1][2]. The following rules apply to percent-encoding within PURL components [1][2]: General Encoding Rules - All non-ASCII characters must be UTF-8 encoded and then percent-encoded [1][2]. - It is permissible to percent-encode any component, with the exception of the type [1][2]. - Producers and consumers must always percent-encode and percent-decode components and segments [1][2]. Separator and Reserved Character Rules - Type: Must not be encoded and must not contain separators [1][2]. - Separators (#,?, @,:) and delimiters (/, =) have specific handling: - The ':' (scheme/type separator) and '/' (type/namespace/name/subpath segment separator) must not be percent-encoded, as they are unambiguous [1][2]. - The '=' (qualifiers key/value separator) must not be encoded [1][2]. - The '@' (version separator), '?' (qualifiers separator), and '#' (subpath separator) must be percent-encoded as '%40', '%3F', and '%23' respectively if they appear elsewhere (i.e., when they are not acting as the primary separator for those components) [1][2]. When implementing an SPDX ExternalRef, the PURL is typically stored in the referenceLocator field [3][4]. Adhering to these encoding rules ensures compatibility across different SBOM producers and consumers [1][4].
Citations:
- 1: https://spdx.github.io/spdx-spec/v3.1-RC1/annexes/pkg-url-specification/
- 2: https://spdx.github.io/spdx-spec/v3.0.1/annexes/pkg-url-specification/
- 3: https://konflux-ci.dev/architecture/ADR/0044-spdx-support/
- 4: https://fossa.com/blog/understanding-purl-specification-package-url/
🌐 Web query:
CycloneDX component purl field percent encoding rules
💡 Result:
The Package URL (PURL) specification, which CycloneDX uses for component identification, mandates strict percent-encoding rules to ensure unambiguous parsing [1][2][3]. A PURL is defined as an ASCII string [2]. Key Percent-Encoding Rules: 1. General Requirement: Components and segments within a PURL must be percent-encoded according to RFC 3986 [2][3]. Non-ASCII characters must first be UTF-8 encoded and then percent-encoded [2]. 2. Separator Characters: Specific reserved characters acting as separators must NOT be percent-encoded when used in their designated structural roles [2][3]: - The colon ':' (scheme/type separator) must NOT be encoded [2][3]. - The forward slash '/' (used to separate type/namespace/name segments) must NOT be encoded [2][3]. - The '@' version separator, '?' qualifiers separator, and '#' subpath separator must NOT be encoded when functioning as delimiters [2][3]. - The '=' character used within qualifiers to separate keys and values must NOT be encoded [2]. 3. Encoding Reserved Characters Elsewhere: When reserved characters (such as '@', '?', '#', or ':') appear within a component's data (rather than as its primary separator), they must be percent-encoded to maintain the PURL's integrity [2]. For instance, '@' must be encoded as %40, '?' as %3F, and '#' as %23 when they occur inside a namespace, name, version, or qualifier value [2]. 4. The Type Component: The package 'type' MUST NOT be encoded [2][3]. It must be composed only of ASCII letters, numbers, periods ('.'), and dashes ('-'), and it must start with an ASCII letter [3]. 5. Producers and Consumers: All producers and consumers of PURL data are required to consistently apply these percent-encoding and decoding rules to ensure interoperability across different ecosystems and SBOM formats [2].
Citations:
- 1: https://github.com/package-url/purl-spec/blob/main/README.md
- 2: https://spdx.github.io/spdx-spec/v3.0.1/annexes/pkg-url-specification/
- 3: https://ecma-tc54.github.io/ECMA-427/multipage/purl-specification.html
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "bundled\\(nodejs|golang\\(|bundled\\(|golang\\(" sbom/examples/rpm/build -S || true
rg -n "nodejs\\(|nodejs-\\w+|`@scope`|`@angular`|`@react`" sbom/examples/rpm/build -S || true
rg -n "bundled\\(" -S sbom/examples | head -n 200 || true
ls -R sbom/examples/rpm/build | head -n 200 || trueRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 2281
🌐 Web query:
Package URL (purl) npm scoped package @scope version delimiter percent-encoding guidance
💡 Result:
In the Package URL (purl) specification, scoped npm packages must be formatted by placing the scope in the namespace component and the package name in the name component [1][2]. Because the @ symbol is used as a delimiter for the version in a purl (e.g., name@version), the @ symbol at the beginning of an npm scope must be percent-encoded as %40 to avoid ambiguity and ensure correct parsing [3][4][5][6]. Guidance for construction: 1. Namespace: The scope (including the leading @) must be percent-encoded [1][7]. For example, the scope @angular becomes %40angular [5][8]. 2. Name: The package name itself should be placed in the name component [3][2]. 3. Version Delimiter: The @ symbol used to separate the package name from the version is a reserved separator and must not be percent-encoded in its role as a delimiter [1][9]. 4. Example: A scoped package named @angular/core with version 16.2.0 is represented as pkg:npm/%40angular/core@16.2.0 [8]. It is essential to follow this encoding rule because parsers use the @ character to identify the start of the version string; leaving the scope's @ unencoded can cause the parser to incorrectly identify the version or fail to parse the purl entirely [6]. All producers and consumers of purl data must adhere to these encoding rules to maintain interoperability across ecosystems [3][9].
Citations:
- 1: https://github.com/voltone/purl-spec
- 2: purl of scoped packages CycloneDX/cyclonedx-node-module#56
- 3: https://ecma-tc54.github.io/ECMA-427/multipage/purl-specification.html
- 4: Percent encoding spec and : and / package-url/purl-spec#39
- 5: https://help.sonatype.com/en/package-url-and-component-identifiers.html
- 6: https://forums.inedo.com/topic/4063/scoped-npm-packages-not-listed-in-releases
- 7: https://ecma-international.org/wp-content/uploads/ECMA-427_1st_edition_december_2025.pdf
- 8: https://github.com/interlynk-io/purl-js
- 9: https://spdx.github.io/spdx-spec/v3.0.1/annexes/pkg-url-specification/
🌐 Web query:
Package URL purl encoding rules for pkg:type/namespace/name@version reserved characters percent-encoding
💡 Result:
The Package URL (purl) specification defines a standardized format to represent software package identity, using percent-encoding to ensure components can be parsed unambiguously [1][2][3]. A purl is strictly an ASCII string where non-ASCII characters must be UTF-8 encoded and then percent-encoded according to RFC 3986 [1][2]. The core encoding rules for purl components (namespace, name, version, qualifiers, and subpath) are as follows: 1. Mandatory Separators: Several characters are reserved as structural separators and must NOT be percent-encoded when used for their designated purpose [1][2]: - The colon ':' (after the 'pkg' scheme) [1][2]. - The forward slash '/' (separating type, namespace, name, and subpath segments) [1][2]. - The at sign '@' (version separator) [1][2]. - The question mark '?' (qualifiers separator) [1][2]. - The hash sign '#' (subpath separator) [1][2]. - The equal sign '=' (qualifiers key/value separator) [1][2]. 2. Encoding Reserved Characters: When these reserved characters appear within a component's data (rather than acting as a separator), they must be percent-encoded to prevent parsing ambiguity [1][4][2]. For example: - The '@' separator must be encoded as '%40' if it appears inside a component's value [1]. - The '?' separator must be encoded as '%3F' [1]. - The '#' separator must be encoded as '%23' [1]. - Any reserved character (e.g., '!', "'") that has no special meaning to the purl structure must still be percent-encoded [4]. 3. Type and Component-Specific Rules: - The package 'type' MUST NOT be percent-encoded and must not contain separators [1]. - Components like 'namespace', 'name', 'version', 'qualifiers', and 'subpath' must be percent-encoded [1][5][2]. - Producers and consumers must always perform percent-decoding when consuming data and percent-encoding when producing it [1]. - When percent-encoding is required, the string is first converted to UTF-8 bytes and then each byte is represented by a percent-encoded character triplet (e.g., '%XX') [1][2]. In summary, characters that do not function as designated separators must be percent-encoded if they are not in the 'unreserved' set (alphanumeric characters and '.-_~') [5][2][6]. Reserved characters acting as separators are left unencoded, while those same characters appearing as literal content within a component must be percent-encoded [1][4][2].
Citations:
- 1: https://spdx.github.io/spdx-spec/v3.0.1/annexes/pkg-url-specification/
- 2: https://ecma-tc54.github.io/ECMA-427/
- 3: https://fossa.com/blog/understanding-purl-specification-package-url/
- 4: Clarifications, in particular about version encoding package-url/purl-spec#35
- 5: https://ecma-international.org/wp-content/uploads/ECMA-427_1st_edition_december_2025.pdf
- 6: https://en.wikipedia.org/wiki/Percent-encoding
🌐 Web query:
CycloneDX component purl percent encoding guidance
💡 Result:
CycloneDX guidance for percent-encoding in component purl/bom-ref is ultimately governed by the Package-URL (PURL) specification (ECMA-427), because CycloneDX’s purl field is a PURL string. 1) What percent-encoding must look like (PURL ECMA-427) - Percent-encoding is applied to specific PURL components (e.g., namespace, name, version, subpath) and is defined per component’s rules in ECMA-427: when percent-encoding is required, the component string is encoded as UTF-8 and then each “data octet” is replaced by a percent-encoded “character triplet” using RFC 3986 [1][2]. - In the percent-encoding rules, certain characters must not be percent-encoded (for example alphanumerics, punctuation, and separator characters when used as PURL separators), and the percent sign is not percent-encoded when it is already representing a percent-encoded character [1][2]. - Spaces must be percent-encoded as %20 [1][2]. 2) Canonical guidance on slashes (/) - In the PURL structure, the purl “namespace” and “name” components are separate segments; ECMA-427 specifies that namespace and name are “percent-encoded strings” (i.e., slashes that occur within those segment values are not “structural” separators and therefore can/should be encoded according to the component percent-encoding rules) [2]. 3) Practical CycloneDX ecosystem issue: encoded “/” (e.g., %2F) vs downstream tool expectations - CycloneDX tooling (example: cdxgen) has historically produced %2F in purl/bom-ref for Go modules, which caused interoperability friction with tools like Dependency-Track that couldn’t parse the URL-encoded purl as expected [3]. - A cdxgen issue describes “golang” PURLs containing URL-encoded module names (example: pkg:golang/github.com%2Fapache%2Fthrift@...), and notes that while this is technically defensible, it causes friction with downstream tools [4]. - CycloneDX community/tooling discussions include that this problem was fixed in a later cdxgen version (the reported fix was “Resolved in 10.3.0” for the Go module %2F encoding case) [4] and a separate issue notes a fix shipped in cdxgen version 8.3.3 for %2F appearing in purl/bom-ref [3]. 4) Bottom line guidance you can apply when generating CycloneDX purl strings - Do not “undo” percent-encoding inside purl just to make it look nicer (e.g., turning %2F back into /), because PURL’s grammar relies on correct percent-encoding for components that are treated as data rather than separators [1][2]. - If you are seeing %2F in the purl (common with Go module identifiers), treat it as a symptom of correctly encoding component data per ECMA-427, and handle downstream tool compatibility by using updated CycloneDX tooling or by ensuring your consumer/parsing tooling supports PURL percent-encoding (cdxgen explicitly discussed this interoperability issue and provided fixes in later versions) [3][4].
Citations:
- 1: https://ecma-international.org/wp-content/uploads/ECMA-427_1st_edition_december_2025.pdf
- 2: https://ecma-tc54.github.io/ECMA-427/multipage/purl-specification.html
- 3: urlencoded symbols in purl and bom-ref cdxgen/cdxgen#303
- 4:
golangPURLs contain URL-encoded Go module names cdxgen/cdxgen#897
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show how bundling README describes inputs
sed -n '1,120p' sbom/examples/rpm/build/README.md | awk '{printf "%5d\t%s\n", NR, $0}'
echo "== Search for npm purls in example outputs =="
rg -n "pkg:npm/" sbom/examples/rpm/build/*.cdx.json sbom/examples/rpm/build/*.spdx.json | head -n 100
echo
echo "== Search for raw '@' inside pkg:npm/ occurrences =="
# Match e.g. pkg:npm/@scope/name
rg -n "pkg:npm/[^[:space:]]*@[^[:space:]]*" sbom/examples/rpm/build/*.cdx.json sbom/examples/rpm/build/*.spdx.json || true
echo
echo "== Search for percent-encoded scope marker %40 in npm purls =="
rg -n "pkg:npm/%40" sbom/examples/rpm/build/*.cdx.json sbom/examples/rpm/build/*.spdx.json || true
echo
echo "== Inspect full purl strings around first match if any =="
python3 - <<'PY'
import glob, re
files = glob.glob("sbom/examples/rpm/build/*.cdx.json") + glob.glob("sbom/examples/rpm/build/*.spdx.json")
pat = re.compile(r'("referenceLocator"\s*:\s*")(?P<purl>pkg:[^"]+)(")')
for f in files:
txt=open(f,'r',encoding='utf-8').read()
m=None
for m in pat.finditer(txt):
p=m.group('purl')
if p.startswith("pkg:npm/"):
print("FILE", f)
print(p)
raise SystemExit
print("No pkg:npm purls found")
PYRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 2695
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for npm purls in example outputs (non-fatal if none) =="
rg -n "pkg:npm/" sbom/examples/rpm/build/*.cdx.json sbom/examples/rpm/build/*.spdx.json || true
echo
echo "== Search for pkg:npm/ then any '@' after =="
rg -n "pkg:npm/[^[:space:]]*@[^[:space:]]*" sbom/examples/rpm/build/*.cdx.json sbom/examples/rpm/build/*.spdx.json || true
echo
echo "== Search for percent-encoded npm scope marker %40 in npm purls =="
rg -n "pkg:npm/%40" sbom/examples/rpm/build/*.cdx.json sbom/examples/rpm/build/*.spdx.json || true
echo
echo "== Search for bundled(nodejs( pattern in source/docs =="
rg -n "bundled\\(nodejs\\(|golang\\(nodejs\\(" -S sbom/examples/rpm/build sbom/examples | head -n 200 || true
echo
echo "== Search for '@' inside dep.path construction inputs (nodejs- / npm) in this file =="
rg -n "nodejs-|npm\\b|nodejs\\b" sbom/examples/rpm/build/bundled_provides.pyRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 612
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import glob, re, json
files = glob.glob("sbom/examples/rpm/build/*.cdx.json") + glob.glob("sbom/examples/rpm/build/*.spdx.json")
pat = re.compile(r'^pkg:npm/.*$', re.M)
found=[]
for f in files:
txt=open(f,'r',encoding='utf-8').read()
for m in re.finditer(r'"referenceLocator"\s*:\s*"([^"]+)"', txt):
v=m.group(1)
if v.startswith("pkg:npm/"):
found.append((f,v))
break
print("npm purl count:", len(found))
for f,v in found[:50]:
print(f, "=>", v)
PYRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 108
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="sbom/examples/rpm/build/bundled_provides.py"
wc -l "$FILE"
echo "== Tail (last 160 lines) =="
tail -n 160 "$FILE" | awk '{printf "%6d\t%s\n", NR+('"$(wc -l <"$FILE")'-160), $0}' || true
echo
echo "== Show remaining portion starting at 120 =="
sed -n '120,260p' "$FILE" | awk '{printf "%6d\t%s\n", NR+119, $0}'
echo
echo "== Show remaining portion starting at 260 =="
sed -n '260,420p' "$FILE" | awk '{printf "%6d\t%s\n", NR+259, $0}'
echo
echo "== Find all _bundled_purl call sites in this file =="
rg -n "_bundled_purl\\(" "$FILE"Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 247
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="sbom/examples/rpm/build/bundled_provides.py"
wc -l "$FILE"
echo "== Tail 80 lines =="
tail -n 80 "$FILE" | sed -n '1,80p' | awk '{printf "%6d\t%s\n", NR, $0}'
echo
echo "== Show ranges 120-189 =="
sed -n '120,189p' "$FILE" | awk '{printf "%6d\t%s\n", NR+119, $0}'
echo
echo "== Call sites for _bundled_purl =="
rg -n "_bundled_purl\\(" "$FILE"
echo
echo "== CycloneDX conversion section (look for cyclonedx/lang) =="
rg -n "cyclonedx|bom-ref|component.*purl|referenceLocator|purl" "$FILE"Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 6569
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "bundled\\(nodejs\\(" -S . || true
rg -n "bundled\\(nodejs\\(.*@.*\\/" -S . || true
rg -n "@[a-zA-Z0-9._-]+/" sbom -S || true
rg -n "nodejs\\([^)]*@[^)]*\\)" -S . || true
rg -n "npm scoped|scoped npm|%40|pkg:npm/" -S sbom docs . || trueRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 546
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "pkg:npm/%40" docs sbom -S || true
rg -n "bundled\\(nodejs\\([^)]*@[^)]*\\)" docs/sbom.md -S || true
rg -n "bundled\\(nodejs\\(.*@.*\\/.*\\)\\)" docs/sbom.md -S || true
rg -n "@[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+" docs/sbom.md -S || true
rg -n "nodejs\\-@|bundled\\(nodejs\\(\\@|%40" docs/sbom.md -S || trueRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 336
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "pkg:npm/%40" docs sbom -S || true
rg -n "bundled\\(nodejs\\([^)]*@[^)]*\\)" docs/sbom.md -S || true
rg -n "bundled\\(nodejs\\(.*@.*\\/.*\\)\\)" docs/sbom.md -S || true
rg -n "@[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+" docs/sbom.md -S || true
rg -n "nodejs\\-@|bundled\\(nodejs\\(\\@|%40" docs/sbom.md -S || trueRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 336
🏁 Script executed:
#!/bin/bash
set -euo pipefail
DOC="docs/sbom.md"
echo "== Search for %40 in docs/sbom.md =="
rg -n "%40" "$DOC" || true
echo
echo "== Search for common scoped-npm patterns in docs/sbom.md =="
rg -n "@[a-zA-Z0-9_.-]+/" "$DOC" || true
rg -n "scoped npm|npm scoped|%40|pkg:npm/" "$DOC" || true
echo
echo "== Show bundled dependencies section around line 630-700 =="
sed -n '620,690p' "$DOC" | awk '{printf "%5d\t%s\n", NR+619, $0}'Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 3502
Percent-encode bundled purl paths (npm scoped @... ambiguity)
_bundled_purl injects dep.path verbatim into pkg:{purl_type}/{dep.path}@{version}. For scoped npm provides like bundled(nodejs(@scope/name)), this yields pkg:npm/@scope/name@..., but npm scope @ must be percent-encoded (e.g., %40scope) to avoid ambiguity with the purl version delimiter.
Suggested fix
import hashlib
from dataclasses import dataclass
from typing import Any
+from urllib.parse import quote
@@
def _bundled_purl(dep: BundledDep) -> str:
purl_type = LANG_TO_PURL_TYPE.get(dep.lang, "generic")
ver = f"@{dep.version}" if dep.version else ""
- return f"pkg:{purl_type}/{dep.path}{ver}"
+ encoded_path = "/".join(quote(part, safe="") for part in dep.path.split("/"))
+ return f"pkg:{purl_type}/{encoded_path}{ver}"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _bundled_purl(dep: BundledDep) -> str: | |
| purl_type = LANG_TO_PURL_TYPE.get(dep.lang, "generic") | |
| ver = f"@{dep.version}" if dep.version else "" | |
| return f"pkg:{purl_type}/{dep.path}{ver}" | |
| def _bundled_purl(dep: BundledDep) -> str: | |
| purl_type = LANG_TO_PURL_TYPE.get(dep.lang, "generic") | |
| ver = f"@{dep.version}" if dep.version else "" | |
| encoded_path = "/".join(quote(part, safe="") for part in dep.path.split("/")) | |
| return f"pkg:{purl_type}/{encoded_path}{ver}" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sbom/examples/rpm/build/bundled_provides.py` around lines 42 - 45, The
_bundled_purl function currently inserts dep.path verbatim causing ambiguity for
scoped npm names (e.g., `@scope/name`) — percent-encode the path segment before
building the purl (encode the leading @ as %40 and other reserved characters,
while preserving path separators) by using urllib.parse.quote with safe='/' (or
equivalent) and then return f"pkg:{purl_type}/{encoded_path}{ver}"; update the
function (_bundled_purl) and add the urllib.parse import (or your project's
URL-encoding helper) so scoped npm provides become pkg:npm/%40scope/name@...
| for bundled_component in bundled_cdx_components: | ||
| if bundled_component.get("version") is None: | ||
| bundled_component.pop("version", None) | ||
| self.cdx_components.append(bundled_component) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print the relevant section around the reported line numbers
FILE="sbom/examples/rpm/build/from-koji.py"
nl -ba "$FILE" | sed -n '560,680p'
# Find where bom-ref is set/normalized in this file
rg -n --hidden --no-ignore-vcs "bom-ref|bom_ref|dependsOn|cdx\\[\"dependencies\"\\]" "$FILE"
# Print helper functions / blocks that build component identity or bom-ref
rg -n --hidden --no-ignore-vcs "purl|bom.*ref|component.*ref|cyclonedx" "$FILE"Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 133
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Confirm file exists
ls -la "sbom/examples/rpm/build/from-koji.py"
# Show lines around 580-630 with line numbers (using awk since nl isn't available)
awk 'NR>=560 && NR<=650 {printf "%d\t%s\n", NR, $0}' "sbom/examples/rpm/build/from-koji.py"
# Search for bom-ref / dependency wiring
rg -n --hidden --no-ignore-vcs "bom-ref|bom_ref|dependsOn|cdx\\[\"dependencies\"\\]" "sbom/examples/rpm/build/from-koji.py"
# Search for where bom-ref/purl/component identity is constructed
rg -n --hidden --no-ignore-vcs "purl|bom.*ref|component.*ref|cyclonedx|cdx_components" "sbom/examples/rpm/build/from-koji.py"Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 6698
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="sbom/examples/rpm/build/from-koji.py"
# Show function where bundled provides are converted to CycloneDX components
rg -n "def add_bundled_provides|bundled_provides_to_cdx_components" "$FILE"
# Show the relevant implementations (around the matches)
awk 'NR>=1 && NR<=320 {printf "%d\t%s\n", NR, $0}' "$FILE" | head -n 320
awk 'NR>=320 && NR<=560 {printf "%d\t%s\n", NR, $0}' "$FILE" | head -n 240
awk 'NR>=520 && NR<=640 {printf "%d\t%s\n", NR, $0}' "$FILE" | head -n 200
# Also locate how bom-ref is constructed for syft/spdx components
rg -n "create_cdx_from_spdx\\(|syft_cdx_components|self\\.cdx_components\\.extend\\(syft_cdx_components|pkg:generic/|pkg:rpm/" "$FILE"Repository: RedHatProductSecurity/security-data-guidelines
Length of output: 29053
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="sbom/examples/rpm/build/from-koji.py"
# Show create_cdx_from_spdx implementation
awk 'NR>=640 && NR<=740 {printf "%d\t%s\n", NR, $0}' "$FILE"
# Locate bundled_provides.py and inspect relevant function(s)
ls -la sbom/examples/rpm/build
fd -a "bundled_provides.py" -t f . | head -n 20
# If found, print around bundled_provides_to_cdx_components and bom-ref construction
BFILE="$(fd -a "bundled_provides.py" -t f sbom/examples/rpm/build sbom/examples/rpm -x echo 2>/dev/null | head -n 1 || true)"
if [ -z "${BFILE}" ]; then
BFILE="$(fd -a "bundled_provides.py" -t f . | head -n 1)"
fi
echo "Using bundled providers file: $BFILE"
rg -n "def bundled_provides_to_cdx_components|bom-ref|bom_ref|dependsOn|purl" "$BFILE"
awk 'NR>=1 && NR<=260 {printf "%d\t%s\n", NR, $0}' "$BFILE" | head -n 260
# Print just the function body region more directly
rg -n "def bundled_provides_to_cdx_components" "$BFILE" | head -n 5
start=$(rg -n "def bundled_provides_to_cdx_components" "$BFILE" | head -n 1 | cut -d: -f1)
if [ -n "${start}" ]; then
awk -v s="$start" 'NR>=s && NR<=s+220 {printf "%d\t%s\n", NR, $0}' "$BFILE"
fiRepository: RedHatProductSecurity/security-data-guidelines
Length of output: 11693
Deduplicate bundled CycloneDX components by bom-ref before appending them.
bundled_provides_to_cdx_components()emits"bom-ref"/"purl"from_bundled_purl(b), andcreate_cdx_from_spdx()also uses the package-manager purl as"bom-ref". Since syft components are appended without any"bom-ref"dedupe, a bundled provide can collide with an existing syft/spdx component and produce duplicate CycloneDX component identifiers.cdx["dependencies"][0]["dependsOn"](lines 615-618) is then built from bundled"bom-ref"values, so duplicate ids make dependency references ambiguous/invalid for BOM consumers.
Suggested fix
copy_of_cdx_root["pedigree"] = {"ancestors": cdx_pedigrees}
self.cdx_components.append(copy_of_cdx_root)
+ existing_bom_refs = {component["bom-ref"] for component in self.cdx_components}
for bundled_component in bundled_cdx_components:
if bundled_component.get("version") is None:
bundled_component.pop("version", None)
- self.cdx_components.append(bundled_component)
+ if bundled_component["bom-ref"] not in existing_bom_refs:
+ self.cdx_components.append(bundled_component)
+ existing_bom_refs.add(bundled_component["bom-ref"])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for bundled_component in bundled_cdx_components: | |
| if bundled_component.get("version") is None: | |
| bundled_component.pop("version", None) | |
| self.cdx_components.append(bundled_component) | |
| copy_of_cdx_root["pedigree"] = {"ancestors": cdx_pedigrees} | |
| self.cdx_components.append(copy_of_cdx_root) | |
| existing_bom_refs = {component["bom-ref"] for component in self.cdx_components} | |
| for bundled_component in bundled_cdx_components: | |
| if bundled_component.get("version") is None: | |
| bundled_component.pop("version", None) | |
| if bundled_component["bom-ref"] not in existing_bom_refs: | |
| self.cdx_components.append(bundled_component) | |
| existing_bom_refs.add(bundled_component["bom-ref"]) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@sbom/examples/rpm/build/from-koji.py` around lines 598 - 601, Currently
bundled_cdx_components are appended without checking for existing bom-ref
collisions, which can duplicate CycloneDX component ids; before appending in the
loop inside the routine that handles bundled_cdx_components, compute an
existing_bom_refs set from self.cdx_components (use keys "bom-ref" and fallback
to "purl"), then filter bundled_cdx_components to skip any component whose
"bom-ref" (from _bundled_purl(b) / bundled_provides_to_cdx_components output) is
already present; keep the existing logic that pops version when None, but only
append components whose bom-ref is not in existing_bom_refs so
create_cdx_from_spdx() and the cdx["dependencies"][0]["dependsOn"] entries
remain unambiguous.
Summary
Document how to represent RPM
bundled()/golang()Provides in build-time SBOMs, and update the RPM build example generator to emit them.docs/sbom.md: collect Provides from KojigetRPMDepsacross all build RPMs, model each bundled component as a separate package linked to the SRPM withDEPENDENCY_OF, and use typed purls per ecosystem.sbom/examples/rpm/build/bundled_provides.pywith parsing and SPDX/CycloneDX fragment helpers (mirrors Deptopia / manifest-cube logic).from-koji.pyto collect build Provides and append bundled packages to SPDX and CycloneDX output.vim-9.1.083-5.el10build and release SBOM examples withbundled(libvterm)→pkg:generic/libvterm.Design notes
vim-enhancedprovidesbundled(libvterm)), not the SRPM. They are linked to the SRPM withDEPENDENCY_OFso one build SBOM deduplicates bundled deps across subpackages.pkg:generic/<name>; ecosystem-specific provides map togolang,pypi,npm,gem,cargo, ormavenpurl types.SourceNarchives (CONTAINS) and from components discovered by scanning an unpacked source tree.Test plan
docs/sbom.mdBundled dependencies section for accuracyvim-9.1.083-5.el10SPDX/CycloneDX examples includeSPDXRef-Bundled-11cdd6f19dc1/pkg:generic/libvterm./from-koji.py brew vim-9.1.083-5.el10and diff against committed examples